import sys
import configparser
import os
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QLabel, QLineEdit,
    QPushButton, QVBoxLayout, QHBoxLayout, QComboBox,
    QTableWidget, QTableWidgetItem, QMessageBox, QHeaderView
)
from PySide6.QtGui import QPixmap, QFont
from PySide6.QtCore import Qt, QThread, Signal
import requests
from PIL import Image
import random
from bs4 import BeautifulSoup
import re

#v1.0.1 by deepseek
#author: LiliCould
#date: 2025-03-07

# ==================================================
# 爬虫功能类
# ==================================================
class CourseSpider:
    def __init__(self):
        self.session = requests.Session()
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0",
            "Referer": "http://xkxt.ecjtu.edu.cn/",
            "Host": "xkxt.ecjtu.edu.cn"
        }
        self.session.headers.update(self.headers)

    def get_verify_code(self):
        """获取验证码"""
        try:
            verify_url = f"http://xkxt.ecjtu.edu.cn/servlet/code.servlet?rm={random.random()}"
            response = self.session.get(verify_url, timeout=10)
            return response.content
        except Exception as e:
            print(f"[ERROR] 获取验证码失败：{str(e)}")
            return None

    def login(self, username, password, code):
        """执行登录"""
        login_url = "http://xkxt.ecjtu.edu.cn/login/login_checkout.action"
        data = {
            "username": username,
            "password": password,
            "code": code
        }
        try:
            response = self.session.post(login_url, data=data, allow_redirects=False)
            return response.status_code == 302
        except Exception as e:
            print(f"[ERROR] 登录请求异常：{str(e)}")
            return False

    def get_schedule(self):
        """获取课表"""
        try:
            url = "http://xkxt.ecjtu.edu.cn/Schedule/Schedule_getUserSchedume.action"
            response = self.session.get(url, timeout=10)
            if response.status_code == 200:
                with open("debug_schedule.html", "w", encoding="utf-8") as f:
                    f.write(response.text)
                print("[DEBUG] 已保存课表HTML到debug_schedule.html")
            return response.text if response.status_code == 200 else None
        except Exception as e:
            print(f"[ERROR] 获取课表失败：{str(e)}")
            return None

    def parse_timetable(self, html):
        """解析课表HTML"""
        try:
            soup = BeautifulSoup(html, 'html.parser')
            table = soup.find('table', {'id': 'courseSche'})
            if not table:
                print("[WARNING] 未找到课表主体")
                return {}

            week_tables = {}
            time_slots = ["1-2", "3-4", "5-6", "7-8", "9-10", "11-12"]

            # 遍历所有课程行
            for row in table.find_all('tr')[1:]:  # 跳过标题行
                cells = row.find_all(['td', 'th'])
                if len(cells) < 8:
                    continue

                current_time = cells[0].text.strip()
                
                # 处理每一天的课程
                for day_idx in range(1, 8):  # 周一到周日
                    day_cell = cells[day_idx]
                    courses = self.parse_day_cell(day_cell)
                    
                    for course in courses:
                        for week in course['weeks']:
                            if str(week) not in week_tables:
                                week_tables[str(week)] = self.create_week_structure()
                            self.add_course_to_week(
                                week_tables[str(week)],
                                current_time,
                                day_idx-1,  # 转换为0-based索引
                                course
                            )

            print(f"[DEBUG] 解析到周次数据：{list(week_tables.keys())}")
            return week_tables
        except Exception as e:
            print(f"[ERROR] 解析出错：{str(e)}")
            return {}

    def parse_day_cell(self, cell):
        """解析单个课程单元格"""
        courses = []
        elements = [e.strip() for e in cell.stripped_strings if e.strip()]
        
        # 每3个元素为一门课程
        for i in range(0, len(elements), 3):
            if i+2 >= len(elements):
                break

            try:
                name = elements[i]
                teacher_place = elements[i+1]
                time_info = elements[i+2]

                # 解析教师和地点
                teacher, place = re.split(r'\s*@\s*', teacher_place, 1)
                
                # 解析周次和节次
                week_part, sessions = re.split(r'\s+', time_info, 1)
                weeks = self.parse_week_range(week_part)
                sessions = sessions.replace(' ', '').split(',')

                courses.append({
                    'name': name,
                    'teacher': teacher,
                    'place': place,
                    'weeks': weeks,
                    'sessions': sessions
                })
            except Exception as e:
                print(f"[WARNING] 课程解析失败：{str(e)}")
                continue
        
        return courses

    def parse_week_range(self, week_str):
        """解析周次范围"""
        try:
            if '-' in week_str:
                start, end = map(int, week_str.split('-'))
                return list(range(start, end+1))
            return [int(week_str)]
        except:
            print(f"[WARNING] 无效的周次格式：{week_str}")
            return []

    def create_week_structure(self):
        """创建周课表数据结构"""
        return {
            "1-2": [[] for _ in range(7)],
            "3-4": [[] for _ in range(7)],
            "5-6": [[] for _ in range(7)],
            "7-8": [[] for _ in range(7)],
            "9-10": [[] for _ in range(7)],
            "11-12": [[] for _ in range(7)],
        }

    def add_course_to_week(self, week_data, time_slot, day_idx, course):
        """添加课程到周数据"""
        if time_slot not in week_data:
            return
        
        course_info = f"{course['name']}\n{course['place']}\n{course['teacher']}"
        week_data[time_slot][day_idx].append(course_info)

# ==================================================
# 后台工作线程
# ==================================================
class WorkerThread(QThread):
    update_signal = Signal(dict)
    error_signal = Signal(str)
    verify_code_signal = Signal(bytes)

    def __init__(self, spider, username, password, code=None):
        super().__init__()
        self.spider = spider
        self.username = username
        self.password = password
        self.code = code
        self.mode = "login" if code else "verify_code"

    def run(self):
        try:
            if self.mode == "verify_code":
                if img_data := self.spider.get_verify_code():
                    self.verify_code_signal.emit(img_data)
            elif self.mode == "login":
                if self.spider.login(self.username, self.password, self.code):
                    if schedule_html := self.spider.get_schedule():
                        week_tables = self.spider.parse_timetable(schedule_html)
                        self.update_signal.emit(week_tables)
                    else:
                        self.error_signal.emit("获取课表失败")
                else:
                    self.error_signal.emit("登录失败，请检查凭证和验证码")
        except Exception as e:
            self.error_signal.emit(str(e))

# ==================================================
# 主界面
# ==================================================
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.spider = CourseSpider()
        self.week_tables = {}
        self.init_ui()
        self.load_config()
        self.refresh_verify_code()

    def init_ui(self):
        """初始化界面"""
        self.setWindowTitle("智能课表查询系统")
        self.setGeometry(100, 100, 1200, 800)
        self.setup_style()
        self.create_widgets()
        self.setup_layout()
        self.connect_signals()

    def setup_style(self):
        """设置界面样式"""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f5f7fa;
            }
            QLabel {
                font-size: 14px;
                color: #303133;
            }
            QLineEdit, QComboBox {
                border: 1px solid #dcdfe6;
                border-radius: 4px;
                padding: 8px;
                font-size: 14px;
                min-width: 120px;
            }
            QPushButton {
                background-color: #409eff;
                color: white;
                border: none;
                border-radius: 4px;
                padding: 8px 16px;
                font-size: 14px;
            }
            QPushButton:hover {
                background-color: #66b1ff;
            }
            QPushButton:disabled {
                background-color: #a0cfff;
            }
            QTableWidget {
                background-color: white;
                alternate-background-color: #fafafa;
                gridline-color: #ebeef5;
                font-size: 13px;
            }
            QHeaderView::section {
                background-color: #f5f7fa;
                padding: 8px;
                border: none;
            }
        """)

    def create_widgets(self):
        """创建控件"""
        self.verify_code_label = QLabel()
        self.verify_code_label.setFixedSize(120, 40)
        self.verify_code_input = QLineEdit()
        self.verify_code_input.setPlaceholderText("输入验证码")
        self.refresh_btn = QPushButton("刷新验证码")
        self.login_btn = QPushButton("登录")
        self.week_combo = QComboBox()
        self.table = QTableWidget()

        # 表格设置
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels(["时间段", "周一", "周二", "周三", "周四", "周五", "周六", "周日"])
        self.table.verticalHeader().setVisible(False)
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table.setAlternatingRowColors(True)

    def setup_layout(self):
        """设置布局"""
        control_layout = QHBoxLayout()
        control_layout.addWidget(QLabel("验证码："))
        control_layout.addWidget(self.verify_code_input)
        control_layout.addWidget(self.verify_code_label)
        control_layout.addWidget(self.refresh_btn)
        control_layout.addWidget(self.login_btn)
        control_layout.addStretch()

        main_layout = QVBoxLayout()
        main_layout.setSpacing(15)
        main_layout.addLayout(control_layout)
        main_layout.addWidget(QLabel("选择周次："))
        main_layout.addWidget(self.week_combo)
        main_layout.addWidget(self.table)

        container = QWidget()
        container.setLayout(main_layout)
        self.setCentralWidget(container)

    def connect_signals(self):
        """连接信号槽"""
        self.refresh_btn.clicked.connect(self.refresh_verify_code)
        self.login_btn.clicked.connect(self.start_login)
        self.week_combo.currentTextChanged.connect(self.update_table)

    def load_config(self):
        """加载配置文件"""
        self.config = configparser.ConfigParser()
        if os.path.exists("user.ini"):
            self.config.read("user.ini", encoding="utf-8")
        else:
            self.config["DEFAULT"] = {"username": "", "password": ""}
            with open("user.ini", "w", encoding="utf-8") as f:
                self.config.write(f)
            QMessageBox.information(self, "提示", "请在同级目录的user.ini文件中填写账号密码")

    def refresh_verify_code(self):
        """刷新验证码"""
        self.worker = WorkerThread(self.spider, "", "")
        self.worker.verify_code_signal.connect(self.show_verify_code)
        self.worker.error_signal.connect(self.show_error)
        self.worker.start()

    def show_verify_code(self, img_data):
        """显示验证码"""
        with open("verify_code.png", "wb") as f:
            f.write(img_data)
        pixmap = QPixmap("verify_code.png")
        self.verify_code_label.setPixmap(pixmap.scaled(120, 40, Qt.KeepAspectRatio))

    def start_login(self):
        """开始登录流程"""
        username = self.config["DEFAULT"].get("username", "")
        password = self.config["DEFAULT"].get("password", "")
        code = self.verify_code_input.text()

        if not all([username, password, code]):
            QMessageBox.warning(self, "警告", "请先配置账号密码并输入验证码")
            return

        self.login_btn.setEnabled(False)
        self.login_btn.setText("登录中...")

        self.worker = WorkerThread(self.spider, username, password, code)
        self.worker.update_signal.connect(self.update_data)
        self.worker.error_signal.connect(self.show_error)
        self.worker.finished.connect(lambda: self.login_btn.setEnabled(True))
        self.worker.finished.connect(lambda: self.login_btn.setText("登录"))
        self.worker.start()

    def update_data(self, week_tables):
        """更新课表数据"""
        self.week_tables = week_tables
        self.week_combo.clear()
        if week_tables:
            weeks = sorted(week_tables.keys(), key=lambda x: int(x))
            self.week_combo.addItems(weeks)
            self.update_table()
        else:
            QMessageBox.warning(self, "警告", "未解析到课程数据")

    def update_table(self):
        """更新表格显示"""
        current_week = self.week_combo.currentText()
        if not current_week or current_week not in self.week_tables:
            return

        week_data = self.week_tables[current_week]
        time_slots = ["1-2", "3-4", "5-6", "7-8", "9-10", "11-12"]

        self.table.setRowCount(len(time_slots))
        self.table.setColumnCount(8)

        for row_idx, time_slot in enumerate(time_slots):
            # 设置时间段
            self.table.setItem(row_idx, 0, QTableWidgetItem(time_slot))
            
            # 填充课程数据
            for day_idx in range(7):
                courses = week_data.get(time_slot, [[] for _ in range(7)])[day_idx]
                cell_content = "\n\n".join(courses)
                item = QTableWidgetItem(cell_content)
                item.setTextAlignment(Qt.AlignTop | Qt.AlignLeft)
                self.table.setItem(row_idx, day_idx + 1, item)

        # 调整表格显示
        self.table.resizeRowsToContents()
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)

    def show_error(self, msg):
        """显示错误信息"""
        QMessageBox.critical(self, "错误", msg)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())